//
//  FDWebSocket.m
//  CenturyCity
//
//  Created by nsm on 2018/12/14.
//  Copyright © 2018 beisheng. All rights reserved.
//

#import "FDWebSocket.h"
#import "BSSocketReceiver.h"
#import <CommonCrypto/CommonCryptor.h>

static NSString * const kSaltToken = @"rnIkJZ3CHhE3k5!#";
static NSString * const kSaltAES256 = @"gC#gLeB#z@R33Tde";
static NSString * const kIv = @"0000000000000000";

// 单次连接超时时间
static const NSTimeInterval kConnectTimeout = 1.f;
// 最大重连次数, 默认5次
static const NSUInteger kMaximumReconnectCount = 4;
// 心跳包间隔时间
static const NSTimeInterval kPingInterval = 8.f;

@interface FDWebSocket () <SRWebSocketDelegate>
@property (nonatomic, strong) SRWebSocket *webSocket;
@property (nonatomic, copy)   NSDictionary *param;
@property (nonatomic, strong) GCDTimer *pingTimer;
@property (nonatomic, strong) YYThreadSafeArray *sendBuffer;
@property (nonatomic, strong) GCDTimer *sendTimer;
@property (nonatomic, strong) dispatch_queue_t sendSerialQueue;
@property (nonatomic, strong) BSSocketReceiver *receiver;
@property (nonatomic) NSUInteger reconnectCount;
@end

@implementation FDWebSocket

+ (instancetype)sharedInstance {
    static FDWebSocket *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [self new];
        [instance setup];
    });
    return instance;
}

- (void)setup {
    self.reconnectCount = 0;
    self.sendBuffer = @[].mutableCopy;
    self.sendSerialQueue = dispatch_queue_create("nsm.send.serial", DISPATCH_QUEUE_SERIAL);
}

#pragma mark - setter/getter
- (GCDTimer *)pingTimer {
    if (!_pingTimer) {
        @nsm_weak(self)
        _pingTimer = GCDTimer.new.queue(dispatch_get_global_queue(0, 0))
        .parameters(kPingInterval, kPingInterval)
        .eventHandler(^{
            @nsm_strong(self)
            if (self.webSocket.readyState != SR_OPEN) return;
            // 心跳保活
            [self.webSocket sendPing:nil];
        });
    }
    return _pingTimer;
}

- (GCDTimer *)sendTimer {
    if (!_sendTimer) {
        @nsm_weak(self)
        _sendTimer = GCDTimer.new.queue(self.sendSerialQueue)
        .parameters(0, 0.3)
        .eventHandler(^{
            @nsm_strong(self)
            [self sendFromBuffer];
        });
    }
    return _sendTimer;
}

- (BSSocketReceiver *)receiver {
    if (!_receiver) {
        _receiver = BSSocketReceiver.new;
    }
    return _receiver;
}

- (SRReadyState)status {
    return self.webSocket.readyState;
}

#pragma mark - 加密/解密
- (NSDictionary *)dictionaryWithJSON:(id)json {
    if (!json || json == (id)kCFNull) return nil;
    NSDictionary *dic = nil;
    NSData *jsonData = nil;
    if ([json isKindOfClass:[NSDictionary class]])
        dic = json;
    else if ([json isKindOfClass:[NSString class]])
        jsonData = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
    else if ([json isKindOfClass:[NSData class]])
        jsonData = json;
    
    if (jsonData) {
        dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
        if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
    }
    return dic;
}

- (NSData *)dataWithDictionary:(NSDictionary *)dic {
    NSError *error;
    return [NSJSONSerialization dataWithJSONObject:dic options:kNilOptions error:&error];
}

- (NSString *)MD5EncryptForToken:(NSDictionary *)dict {
    if (!dict.count) return nil;
    
    NSMutableDictionary *mutDic = [NSMutableDictionary dictionaryWithDictionary:dict];
    // 1.排序从大到小
    NSArray *keys = [mutDic.allKeys sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        return [obj2 compare:obj1 options:NSLiteralSearch];
    }];
    // 2.加盐
    NSMutableArray *items = [NSMutableArray new];
    for (NSString *key in keys) {
        [items addObject:[NSString stringWithFormat:@"%@=%@", key,mutDic[key]]];
    }
    [items addObject:kSaltToken];
    // 3.md5
    return [[items componentsJoinedByString:@"&"] md5String];
}

#pragma mark - 连接/关闭/重连
- (void)connect:(NSDictionary *)param {
    self.param = param;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self doConnect];
    });
}

- (void)doConnect {
    // 清空之前的连接
    [self close];

    NSMutableArray *params = @[].mutableCopy;
    for (NSString *key in self.param.allKeys) {
        [params addObject:FormatString(@"%@=%@",key, self.param[key])];
    }
    // 加入时间戳
    [params addObject:FormatString(@"timestamp=%@", @(time(NULL)))];
    // 加入token
    [params addObject:FormatString(@"token=%@", [self MD5EncryptForToken:self.param])];
    
    NSString *url = CurrentSocketDomain();
    self.webSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
    self.webSocket.delegate = self;
    
    [self.webSocket open];
}

- (void)close {
    if (!self.webSocket) return;
    
    [self.webSocket close];
    self.webSocket.delegate = nil;
    self.webSocket = nil;
}

- (void)reconnect {
    // 已经重连上,这个判断其实是多余的,保险起见吧
    if (self.webSocket.readyState == SR_OPEN) return;
    // 重连次数到达上限（重置一个周期）
    if (self.reconnectCount > kMaximumReconnectCount) self.reconnectCount = 0;
    self.reconnectCount++;
    // 连接超时,重新重连(时间间隔线性增长)
    NSTimeInterval delayInSeconds = kConnectTimeout*self.reconnectCount;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
        // 重连回调
        !self.didReconnecting?:self.didReconnecting(self.reconnectCount);
        [self doConnect];
    });
}

#pragma mark - 发送/接收
- (void)send:(NSDictionary *)dataDict main:(int)main sub:(int)sub {
    // 1.组装数据
    NSDictionary *sendDict = @{@"main": IntegerString(main),
                               @"sub": IntegerString(sub),
                               @"content": dataDict};
    // 2.数据转成data
    NSData *sendData = [self dataWithDictionary:sendDict];
    // 3.AES加密key
    NSData *keyData = [kSaltAES256 dataUsingEncoding:NSUTF8StringEncoding];
    // 4.向量iv
    NSData *ivData = [kIv dataUsingEncoding:NSUTF8StringEncoding];
    // 5.AES加密
    NSData *encodeAESData = [sendData aes256EncryptWithKey:keyData iv:ivData];
    // 6.Base64加密
    NSString *encodeBase64String = [encodeAESData base64EncodedString];
    
    // 将数据加入发送缓冲区
    [self.sendBuffer addObject:encodeBase64String];
}

- (void)receive:(id)data {
    !self.didReceiveDataBefore?:self.didReceiveDataBefore(data);
    
    // 1.转换服务器的json字符串
    NSDictionary *dict = [self dictionaryWithJSON:data];
    // 2.Base64解密data字段
    NSData *decodeBase64Data = [NSData dataWithBase64EncodedString:dict[@"data"]];
    // 3.AES解密key
    NSData *keyData = [kSaltAES256 dataUsingEncoding:NSUTF8StringEncoding];
    // 4.向量iv
    NSData *ivData = [kIv dataUsingEncoding:NSUTF8StringEncoding];
    // 4.AES解密
    NSData *decodeAES256Data = [decodeBase64Data aes256DecryptWithkey:keyData iv:ivData];
    // 5.得到原始数据
    NSDictionary *dataDic = [self dictionaryWithJSON:decodeAES256Data];
    
    // 接收无需缓冲区,这里直接转发
    [self.receiver didRecvData:dataDic];
    !self.didReceiveData?:self.didReceiveData(dataDic);
}

- (void)sendFromBuffer {
    // 断网会自动设置会nil
    if (!self.webSocket) return;
    // 只有SR_OPEN状态下才能发送
    if (self.webSocket.readyState != SR_OPEN) return;
    // 判断缓冲区有没有数据
    if (!self.sendBuffer.count) return;

    id data = self.sendBuffer.firstObject;
    [self.webSocket send:data];
    !self.didSendData?:self.didSendData(data);
    [self.sendBuffer removeFirstObject];
}

#pragma mark - SRWebSocketDelegate
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
    !self.didConnectSuccess?:self.didConnectSuccess();
    // 开启心跳检测
    self.pingTimer.resume();
    // 开始发送数据
    self.sendTimer.resume();
    // 重置连接次数
    self.reconnectCount = 0;
}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
    !self.didConnectFailure?:self.didConnectFailure(error);
    // 连接失败,重连
    [self reconnect];
}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
    [self receive:message];
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
    // 暂停心跳检测
    self.pingTimer.pause();
    // 暂停发送数据
    self.sendTimer.pause();
    !self.didClose?:self.didClose(code, reason, wasClean);
    
    /*
     1000正常关闭, 1001服务器关闭
     手动close:1000 不会走这里因为delegate为nil
     */
    [self reconnect];
}

- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
    !self.didReceivePong?:self.didReceivePong(pongPayload);
}

@end
